<?php
/**
 * Created by PhpStorm.
 * User: joe
 * Date: 17-11-5
 * Time: 下午3:13
 */

namespace System;

class Router
{
    /**
     * @var array
     * Contain all routes
     */
    private $routes = [];
    /**
     * @var
     * Contain all matches
     */
    private $matches;
    private $preg = [
        ':n' => '[0-9]+', //num
        ':nn' => '[^0-9]+', // not num
        ':s' => '[A-Za-z]+', // string
        ':sn' => '[A-Za-z0-9]+', // string & num
        ':a' => '.+' // all
    ];


    public function __construct()
    {
        $config = new \System\Config('Router');

        if ($config->router) {
            foreach ($config->router as $route => $controller) {
                $this->add($route, $controller);
            }
        }
    }

    /**
     * @param $route
     * @param $controller
     * Example : Router::add('/posts/{id}', 'post@listByID')
     */
    public function add($route, $controller)
    {
        $route = trim($route, '/');
        $this->routes[$route] = $controller;
    }

    /**
     * @return mixed
     * When set all routes, run the Router Application !
     */
    public function run()
    {
        foreach ($this->routes as $route => $controller) {
            if ($this->match($_SERVER['QUERY_STRING'], $route)) {
                return $this->call($controller);
            }
        }
        $this->findPath($_SERVER['QUERY_STRING']);
    }

    public function findPath($url)
    {
        $urls = explode('/', $url);

        if (preg_match('/^\w+(&)\.?/', $urls[count($urls) - 1])) {
            $p = explode('&', $urls[count($urls) - 1]);
            unset($urls[count($urls) - 1]);
            $urls[] = $p[0];
        } elseif ($urls[count($urls) - 1] == 'index.php') {
            unset($urls[count($urls) - 1]);
        }
        $urls = array_filter($urls);

        $classFile = BASE_PATH . '/App/Controllers';
        $controller_name = NULL;
        $controllerName = NULL;
        $count = count($urls);
        $i = 0;
        foreach ($urls as $u) {
            $classFile .= '/' . ucfirst(array_shift($urls));
            $controllerName .= "\\" . ucfirst($u);
            if (file_exists($classFile . '.php')) {
                $controller_name = "\\App\\Controllers{$controllerName}";
                break;
            } else if ($i == ($count - 1) && file_exists($classFile . '/Index.php')) {
                $controller_name = "\\App\\Controllers{$controllerName}\\Index";
                break;
            }
            $i++;
        }

        $function = array_shift($urls) ?: 'index';

        if ($controller_name && method_exists($controller_name, $function)) {
            return call_user_func_array([new $controller_name(), $function], $urls);
        } else {
            $this->error();
        }
    }

    /**
     * @param $url
     * @param $route
     * @return bool
     * Is a match in routes with url ?
     */
    public function match($url, $route)
    {
        foreach ($this->preg as $key => $value) {
            $route = preg_replace('#' . $key . '#', '(' . $value . ')', $route);
        }
        $url = rtrim($url, '/');
        if (!preg_match("#^$route$#", $url, $matches)) {
            return false;
        } else {
            array_shift($matches);
            $this->matches = $matches;
            return true;
        }
    }

    /**
     * @param $controller
     * @return mixed
     * Call the controller and call method with args
     */
    public function call($controller)
    {
        $params = explode('@', $controller);
        $controller_name = "\\App\\Controllers\\{$params[0]}";
        $controller = new $controller_name();
        if (method_exists($controller_name, $params[1])) {
            return call_user_func_array([$controller, $params[1]], $this->matches);
        } else {
            $this->error();
        }
    }

    /**
     *  Route is incorrect
     */
    public function error()
    {
        //header("status: 404 not found");
        showError('404 Not found the page...<pre>' . $_SERVER['QUERY_STRING'] . '</pre>', 400);
    }
}
